/*	--- 对称加密算法 --- 

encrypt	 v.  把…加密(或编码)


'对称加密算法'就是传统的用'同一个密码'进行加密和解密。

例如，我们常用的WinZIP和WinRAR对压缩包的加密和解密，就是使用对称加密算法.

------------------------

从程序的角度看，所谓加密，就是这样一个函数，它接收密码和明文，然后输出密文：

	secret = encrypt(key, message); // message是需要加密的数据

而解密则相反，它接收密码和密文，然后输出明文：

	plain = decrypt(key, secret); // secret是加密后的数据

// plain  n.  清楚的;明显的;浅白的;坦诚的;直率的;直接的;不尚修饰的;朴素的;简单的

------------------------------------

在软件开发中，常用的对称加密算法有：

算法		密钥长度		工作模式(参数)			填充模式(格式选择)
DES		56/64	ECB/CBC/PCBC/CTR/...		NoPadding/PKCS5Padding/...

AES		128/192/256	ECB/CBC/PCBC/CTR/...	NoPadding/PKCS5Padding/PKCS7Padding/...

IDEA	128			ECB						PKCS5Padding/PKCS7Padding/...


密钥长度直接决定加密强度，而'工作模式'和'填充模式'可以看成是对称加密算法的参数和格式选择。

Java标准库提供的算法实现并不包括所有的工作模式和所有填充模式，但是通常我们只需要挑选常用的使用就可以了。

最后注意，DES算法由于密钥过短，可以在短时间内被暴力破解，所以现在已经不安全了。


------------------------------------


#	使用AES加密

AES算法是目前应用最广泛的加密算法。

我们先用ECB模式加密并解密：	*/
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;

public class Main {
	public static void main(String[] args) throws Exception {
		// 原文
		String message = "Hello, world !"; // 明文
		System.out.println("Message: " + message);

		// 128位密钥 = 16 bytes Key
		byte[] key = "1234567890abcdef".getBytes("UTF-8"); 
			// 用于'密码(Cipher)'
		byte[] data = message.getBytes("UTF-8"); 
			// 将'明文'-> 二进制数据

		// 加密
		byte[] encrypted = encrypt(key, data);
			// encrypted(byte[], byte[] 输入)
		System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted)); // Encoder n. 编码器

		// 解密
		byte[] decrypted = decrypt(key, encrypted); // 调用encrypted(byte[], byte[] 输入)
		System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
	}


	// 加密_函数
	public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
		Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // cipher  n.  密码,暗号
		SecretKey sk = new SecretKeySpec(key, "AES"); // 以'AES'方式,加密key

		cipher.init(Cipher.ENCRYPT_MODE, sk); // Cipher.init(Cipher.模式, key)

		return cipher.doFinal(input); // 处理input的byte[]
	}


	// 解密_函数
	public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
		Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 获取Cipher实例
		SecretKey sk = new SecretKeySpec(key, "AES"); 

		cipher.init(Cipher.DECRYPTED_MODE, sk); // Cipher.init(Cipher.模式, key)

		return cipher.doFinal(input); // 处理input的byte[]
	}
}


/*
Java标准库提供的对称加密接口非常简单，使用时按以下步骤编写代码：

	1.根据算法名称/工作模式/填充模式获取Cipher实例；
	2.根据算法名称初始化一个SecretKey实例，密钥必须是指定长度；
	3.使用SerectKey初始化Cipher实例，并设置加密或解密模式；
	4.传入明文或密文，获得密文或明文。

------------------------------------------


ECB模式是最简单的AES加密模式，它只需要一个固定长度的密钥，固定的明文会生成固定的密文.


这种一对一的加密方式会导致安全性降低，更好的方式是通过CBC模式，它需要一个随机数作为IV参数，这样对于同一份明文，每次生成的密文都不同。

CBC工作模式 代码：	*/
import java.security.*;
import java.util.Base64;
import javax.crypto.*;
import javax.crypto.spec.*;

public class Main {
	public static void main(String[] args) throws Exception {
		// 原文
		String message = "Hello world !";
		System.out.println("Message: " + message);

		// 256位密钥 = 32 bytes Key:
		byte[] key = "1234567890abcdef1234567890abcdef".getBytes("UTF-8");
		byte[] data = message.getBytes("UTF-8");


		// 加密
		byte[] encrypted = encrypt(key, data);
		System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));


		// 解密
		byte[] decrypted = decrypted(key, encrypted);
		System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
	}


	// 加密 Function
	public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		SecretKeySpec sks = new SecretKeySpec(key, "AES");

		// CBC模式,需要生成一个16 bytes的initialization vector
		SecureRandom sr = SecureRandom.getInstanceStrong();
		byte[] iv = sr.generateSeed(16); 
			// SecureRandom.generateSeed(16) -> byte[]

		IvParameterSpec ivps = new IvParameterSpec(iv);
		cipher.init(Cipher.ENCRYPT_MODE, sks, ivps);

		byte[] data = cipher.doFinal(input);

		return join(iv, data); 
			// IV不需要保密,和加密编码join发回去
	}


	// 解密
	public static byte[] decrypt(byte[] key, byte[] input) throws GeneralSecurityException {
		// 把input分割成Initilization Vector和密文
		byte[] iv = new byte[16];
		byte[] data = new byte[input.length - 16];
		System.arraycopy(input, 0, iv, 0, iv.length);
		System.arraycopy(input, iv.length, data, 0, data.length);

		// 初始化 解密模块
		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		SecretKeySpec sks = new SecretKeySpec(key, "AES");
		IvParameterSpec ivps = new IvParameterSpec(iv);
		cipher.init(Cipher.DECRYPTED_MODE, sks, ivps); 
			// Cipher.init(模式, 加密格式的key, 初始化向量参数规格)

		return cipher.doFinal(data);
	}


	public static byte[] join(byte[] b1, byte[] b2) {
		byte[] r = new byte[b1.length + b2.length];
		System.arraycopy(b1, 0, r, 0, b1.length);
		System.arraycopy(b2, 0, r, b1.length, b2.length);

		return r;
	}
}


/*
1.在CBC模式下,需要一个随机生成的16字节IV参数,必须使用SecureRandom生成。
	
	SecureRandom SR = SecureRandom.getInstanceStrong();
	byte[] iv = SR.generateSeed(16); 
		// SecureRandom.generateSeed(16) -> byte[]


2.因为多了一个IvParameterSpec实例，因此，初始化方法需要调用Cipher的一个重载方法并传入IvParameterSpec。
	
	IvParameterSpec ivps = new IvParameterSpec(iv);
	cipher.init(Cipher.DECRYPTED_MODE, sks, ivps); 


3.观察输出，可以发现每次生成的IV不同，密文也不同。


---------------------------------------------------*/



/*	---- 对称加密算法 の 小结 ---- 

1. 对称加密算法使用'同一个密钥'进行【加密和解密】,常用算法有：DES、AES和IDEA等;
	

2. 密钥长度由算法设计决定，AES的密钥长度是128/192/256位；


3. 使用对称加密算法需要指定 1.算法名称 2.工作模式 3.填充模式。
	


*/

public static byte[] encrypt(byte[] key, byte[] input) throws GeneralSecurityException {
	Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
		// "AES/CBC/PKCS5Padding" <- 填充模式
	
	SecretKeySpec sks = new SecretKeySpec(key, "AES"); 
		// "AES" <- 算法名称

	// CBC模式,需要生成一个16 bytes的initialization vector
	SecureRandom sr = SecureRandom.getInstanceStrong();
	byte[] iv = sr.generateSeed(16); 
		// SecureRandom.generateSeed(16) -> byte[]

	IvParameterSpec ivps = new IvParameterSpec(iv);
	cipher.init(Cipher.ENCRYPT_MODE, sks, ivps);
		// 3个参数,调用了CBC工作模式

	byte[] data = cipher.doFinal(input);
	
	return join(iv, data); 
		// IV不需要保密,和加密编码join发回去
}















